<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<style>
.content{
   width: 640px;
   height: 640px;
   margin:0 auto;
   position: relative;
}
.btn{
   width: 100%;
   height: 100%;
   position: absolute;
   left: 0;
   top: 0;
   background-color: rgba(0,0,0,0.3);
   z-index: 2;
}
.btn button{
   background:none;
   border:none;
   background-size: 100% 100%;
   cursor: pointer;
   position: absolute;
   left: 50%;
   top: 50%;
}
.startn button{
   width: 200px;
   height: 80px;
   margin: -40px 0 0 -100px;;
   background-image: url('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg4.a0bi.com%2Fupload%2FarticleResource%2F20170922%2F1506016239203.gif&refer=http%3A%2F%2Fimg4.a0bi.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1654932647&t=2eba36dd39a6522d2d9cd0326d2c4dcd');
}
.pastn button{
   width: 70px;
   height:70px;
   margin: -35px 0 0 -35px;
   background-image: url('./start.png');
}
.pastn{
   display:none
}
#snake{
   width: 600px;
   height: 600px;
   background-color: #225675;
   border:20px solid #7dd9ff;
   position: relative;
}
.snakehead{
   background-image: url('./snake.png');
   background-size: cover;
}
.snakebody{
   background-color: #9ddbb1;
   border-radius: 50%;
}
.food{
   background-image: url('./food.png');
   background-size: cover;
}
</style>
<body>
   <div class="content">
        <div class="btn startn">
           <button></button>
        </div>
        <div class="btn pastn">
            <button></button>
        </div>
        <div id="snake">
        </div>
   </div>
</body>
<script>
   var sw=20   //方块的宽
   var sh=20   //方块的高
   var tr=30   //行数
   var td=30   //列数
   var sanke=null //蛇的实例
   var food=null  //食物的实例
   var game=null  //游戏的实例
   //方块构造函数 用来创建每个方块
function Square(x,y,classname){  //x和y表示方块的坐标 classname表示样式类名
   //0 0     0,0   //将x轴坐标和y轴坐标进行换算分别乘上方块的宽和高
   //20 0    1,0
   //40 0    2,0
      this.x=x*sw   //因为传进来的x,y是0,0   1,0   2,0的形式所以乘上方块的宽和高
      this.y=y*sh
      this.class=classname  
      this.viewcontent=document.createElement("div")   //创建每个小方块的dom元素
      this.viewcontent.className=this.class  //给每个小方块绑定类名
      this.parent=document.getElementById('snake')  //获取每个小方块应该添加的父级元素
}
Square.prototype.crate=function(){  //为构造函数的原型身上添加一个方法/创建方块将每个方块添加到页面里面
      this.viewcontent.style.position='absolute'
      this.viewcontent.style.width=sw+'px'
      this.viewcontent.style.height=sh+'px'
      this.viewcontent.style.left=this.x+'px'
      this.viewcontent.style.top=this.y+'px'
      this.parent.appendChild(this.viewcontent)
}
Square.prototype.remove=function(){ //创建一个方法将dom元素从父元素移出
   this.parent.removeChild(this.viewcontent)
}
//创建蛇身上的信息
function Snake(){
   this.head=null   //存一下蛇头的信息
   this.tail=null   //存一下蛇尾的信息
   this.pos=[]      //存储蛇身上每个方块的位置  是一个二维数组
   this.directionNum={
      left:{  //代表蛇往左边走
         x:-1,
         y:0,
         rotate:180  //蛇头在不同的不向应该进行旋转
      },
      right:{ //代表蛇向右走
         x:1,
         y:0,
         rotate:0
      },
      up:{  //代表蛇向上走
         x:0,
         y:-1,
         rotate:-90
      },
      down:{ //代表蛇向下走
         x:0,
         y:1,
         rotate:90
      }

   } //存储蛇走的方向
}
Snake.prototype.init=function(){ //用来初始化蛇的方法
     //创建蛇头
     var sankehead=new Square(2,0,'snakehead')
     sankehead.crate()
     this.head=sankehead   //存储蛇头信息
     this.pos.push([2,0])  //把蛇头的位置存储起来
     //创建蛇身体1
     var snakebody1=new Square(1,0,'snakebody')
     snakebody1.crate()
     this.pos.push([1,0])  //把身体1的坐标存储一下
     //创建蛇身体2
     var snakebody2=new Square(0,0,'snakebody')
     snakebody2.crate()
     this.pos.push([0,0])  //把身体2的坐标存储一下
     this.tail=snakebody2  //将蛇尾存储起来
     //形成链表关系 让蛇身的每个方块和前后方块都有关系 next代表每个方块左边的方块,last代表每个方块右边的方块
     sankehead.last=null    //蛇头
     sankehead.next=snakebody1

     snakebody1.last=sankehead   //蛇身体1
     snakebody1.next=snakebody2

     snakebody2.last=snakebody1  //蛇身体2
     snakebody2.next=null

     //给蛇添加一条属性,用来表示蛇走的方向
     this.direction=this.directionNum.right  //默认让蛇往右走
}
Snake.prototype.getnextpos=function(){ //这个方法用来获取蛇头下一个位置对应的元素,要根据元素做不同的事情
   var nextpos=[ //蛇头要走的下一个点的坐标
       this.head.x/sw+this.direction.x,
       this.head.y/sh+this.direction.y
]
   //下个点是自己 代表撞到了自己 游戏结束
   var selfColleied=false ; //是否撞到自己 默认不是
   this.pos.forEach((value)=>{
      if(value[0]==nextpos[0] && value[1]==nextpos[1]){  //如果数组中的两个数据都相等就说明下一个点在蛇身里面可以找到 代表撞到自己
         selfColleied=true
      }
  })
  //注意 不管是撞到身体、撞到墙、吃到食物都应该return退出函数不向下执行
  if(selfColleied){ //代表撞到自己的身体
          this.strategies.die.call(this)
          return
    }
   //下个点是墙，代表撞到了墙 代表游戏结束
  if(nextpos[0]<0 || nextpos[1]<0 || nextpos[0]>td-1 || nextpos[1]>tr-1)
  {
          this.strategies.die.call(this)
          return
  }

   //下个点是食物,要吃
  if(food && food.pos[0]==nextpos[0] && food.pos[1]==nextpos[1]){
        //如果这个条件成立 说明蛇头要走的下一个点是食物的那个点
        this.strategies.eat.call(this)
        return
  }
   //下个点什么都不是,要动起来
   this.strategies.move.call(this)
}
//用来处理蛇碰撞后要做的事
Snake.prototype.strategies={
      move:function(format){  //代表继续走  format这个参数决定要不要删除蛇尾,当传了这个参数后就表示要吃食物,不传就正常走
         //创建一个新的身体(在旧蛇头的位置)
         var newbody=new Square(this.head.x/20,this.head.y/20,'snakebody')
          //更新链表的关系
          newbody.next=this.head.next  //用原来的蛇头求出身体1,然后给新创建的身体一个next属性连接身体1
          newbody.next.last=newbody    //然后给原来身体1一个last属性连接的是新创建的newbody身体
          newbody.last=null            
          this.head.remove()  //把旧蛇头去除
          newbody.crate()     //把新创建身体加入到页面中
          //创建新的蛇头(新蛇头下一个要走的点就是nextpos)
          var newhead=new Square(this.head.x/sw+this.direction.x,this.head.y/sw+this.direction.y,'snakehead')
          //更新链表关系
          newhead.next=newbody
          newhead.last=null
          newbody.last=newhead
          newhead.crate()   //将新蛇头放到页面上
          newhead.viewcontent.style.transform=`rotate(${this.direction.rotate}deg)`
          //更新蛇身上每一个方块的坐标
          //在数组最前面插入新的蛇头 因为原来的蛇头和新创建的身体是同样的坐标,所以只需要将新的蛇头坐标插入数组最前面就行
           this.pos.splice(0,0,[this.head.x/sw+this.direction.x,this.head.y/sw+this.direction.y]) 
           this.head=newhead //还要把蛇头信息更新一下
           if(!format){ //如果format的值为false 表示没有吃到食物需要删除蛇尾并更新蛇尾信息
               //更新链表
                this.tail.remove()
                //将身体1作为蛇尾
                this.tail=this.tail.last  
               //更新蛇坐标的数组删除最后一项
                this.pos.pop()
           }
      }, 
      eat:function(){ //代表吃食物
          this.strategies.move.call(this,true)
          createFood()  //如果吃到了再随机生成一个新的食物
          game.score++
      },
      die:function(){  //代表游戏结束
          game.over()
      }
}
snake=new Snake()
function createFood(){ //创建随机食物的函数
    var x=null  //食物小方块的随机坐标
    var y=null  

    var include=true  //循环跳出的条件 true表示生成食物的坐标在蛇身上(需要继续循环),false表示食物的坐标不在蛇身上
    while(include){
        var cont=0
        x=Math.round(Math.random()*(td-1))  //随机0-29的函数公式
        y=Math.round(Math.random()*(tr-1))
        snake.pos.forEach((item)=>{  //将蛇身体的每个坐标循环一遍
            if(item[0]!=x || item[1]!=y){  //说明食物没有在蛇身上
                  cont++
            }
        })
        if(cont==snake.pos.length){
               include=false
         }
    }
   var foodDom=document.querySelector('.food')
    //生成食物
    if(foodDom){ //判断第一次有没有dom元素 如果没有就添加到页面上,如果有就不加,这样就不会添加多余的dom元素
       foodDom.style.left=x*sw+'px'
       foodDom.style.top=y*sw+'px'
    }else{
       food=new Square(x,y,'food')
       food.crate()  
    }
    //存储一下生成食物的坐标,用于跟蛇头要走的下一个点进行对比
    food.pos=[x,y] 
}

//创建游戏逻辑
function Game(){ //整个Game代表的是贪吃蛇游戏的逻辑
   this.timer=null
   this.score=0
}
Game.prototype.init=function(){ //开始整个游戏
   snake.init()  //初始化蛇的信息
   createFood()  //随机生成食物
   jianpan()  //开启键盘事件
   this.start()  //开启游戏逻辑
}
function jianpan(){
   document.onkeydown=function(e){ //给键盘绑定事件 获取用户按下的键
        if(e.keyCode==37 && snake.direction!=snake.directionNum.right){//当用户按下左键时 这条蛇不能是正在往右走
            snake.direction=snake.directionNum.left
        }else if(e.keyCode==38 && snake.direction!=snake.directionNum.down){//当用户按上键时 这条蛇不能是往下走
              snake.direction=snake.directionNum.up
        }else if(e.keyCode==39 && snake.direction!=snake.directionNum.left){//当用户按下右键时 这条蛇不能是正在往左走
                       snake.direction=snake.directionNum.right
        }else if(e.keyCode==40 && snake.direction!=snake.directionNum.up){ //当用户按下键时 这条蛇不能是正在往上走
                        snake.direction=snake.directionNum.down
        }
   }
}
Game.prototype.start=function(){ //开始游戏
    this.timer=setInterval(()=>{
      snake.getnextpos()
    },200)
}
Game.prototype.over=function(){  //代表撞到墙或者撞到自己 游戏结束
   clearInterval(this.timer)  //清除定时器
   alert('你的得分为'+this.score)
   //将游戏初始化
   snake=new Snake()  //将snake 初始化
   game=new Game()    //将game  初始化
   document.querySelector('#snake').innerHTML=''  //将snake里面的dom元素都置空
   document.querySelector('.startn').style.display='block'  //将开始游戏显示出来
}
game=new Game()
//开启游戏
var startn=document.querySelector('.startn button')
startn.onclick=function(){
    startn.parentNode.style.display='none'
    game.init()
}
//暂停游戏
document.querySelector('#snake').onclick=function(){  
     clearInterval(game.timer)
     document.querySelector('.pastn').style.display='block'
     document.onkeydown=null
}
document.querySelector('.pastn button').onclick=function(){ //再开启游戏
     game.start()
     document.querySelector('.pastn').style.display='none'
     jianpan()
}
</script>
</html>